<?php

/**
 * Description of Memcache
 *
 * @author superx
 * @package Core
 */
class Core_Memcache {
    const TTL_ONE_MINUTE = 60;
    const TTL_ONE_HOUR = 3600;
    const TTL_ONE_DAY = 86400;
    const TTL_MAX = 2592000; //30 days

    protected static $_instance;
    /**
     *
     * @var Memcached
     */
    protected $memcache;
    protected $name;

    protected function __construct($name = 'default') {
        $this->name = $name;
        $this->memcache = new Memcached();
        $servers = Core_Config::get("memcache.$name");
        foreach ($servers as $conf) {
            $this->memcache->addServer($conf['host'], $conf['port']);
        }
    }

    /**
     *
     * @param string $name
     * @return Core_Memcache
     */
    public static function getInstance($name = 'default') {
        if (!isset(self::$_instance[$name]) || !self::$_instance[$name]) {
            self::$_instance[$name] = new Core_Memcache($name);
        }

        return self::$_instance[$name];
    }

    public function getMc() {
        return $this->memcache;
    }

    public function add($key, $value, $timeout = self::TTL_MAX) {
        try {
            Core_Stats::$memcacheCnt++;
            if (Core_Config::get('Core.SQLDebug')) {
                Core_Log::log("Add {$key}", 'MCDebug');
            }
            return $this->memcache->add($key, $value, $timeout);
        } catch (Exception $e) {
            return false;
        }
    }

    public function get($key) {
        try {
            Core_Stats::$memcacheCnt++;
            if (Core_Config::get('Core.SQLDebug')) {
                Core_Log::log("Get {$key}", 'MCDebug');
            }
            return $this->memcache->get($key);
        } catch (Exception $e) {
            return false;
        }
    }

    /**
     * @param array $keys
     * @return array
     */
    public function getMulti(array $keys) {
        try {
            Core_Stats::$memcacheCnt++;
            if (Core_Config::get('Core.SQLDebug')) {
                Core_Log::log("mGet " . implode(',', $keys), 'MCDebug');
            }
            $ret = $this->memcache->getMulti($keys);
            return $ret ? $ret : array();
        } catch (Exception $e) {
            return array();
        }
    }

    public function set($key, $value, $ttl = self::TTL_MAX) {
        try {
            Core_Stats::$memcacheCnt++;
            if (Core_Config::get('Core.SQLDebug')) {
                Core_Log::log("Set {$key}", 'MCDebug');
            }
            return $this->memcache->set($key, $value, $ttl);
        } catch (Exception $e) {
            return false;
        }
    }

    /**
     * @param array $items
     * @param int $expiration
     * @return bool
     */
    public function setMulti(array $items, $expiration = self::TTL_MAX) {
        try {
            Core_Stats::$memcacheCnt++;
            if (Core_Config::get('Core.SQLDebug')) {
                Core_Log::log("mSet " . implode(',', array_keys($items)), 'MCDebug');
            }
            return $this->memcache->setMulti($items, $expiration);
        } catch (Exception $e) {
            return false;
        }
    }

    /**
     * 为和老版本兼容采取的同步del
     * 在全部迁移完成后可以废除
     *
     * @param $key
     * @param int $timeout
     *
     * @return bool
     */
    public function del($key, $timeout = 0) {
        $mapKey = $this->getKeyMap($key);
        $this->_delMulti($mapKey, $timeout);
        return $this->_del($key, $timeout);
    }

    public function delMulti(array $keys, $timeout = 0) {
        $mapKey = $this->getKeyMap($keys);
        $this->_delMulti($mapKey, $timeout);
        return $this->_delMulti($keys, $timeout);
    }

    public function getKeyMap($keys) {
        $map = Core_Config::get('mcKeyMap');

        $to_del = [];

        foreach ((array)$keys as $key) {
            if (strpos($key, ':') !== false) {
                //newroad -> old
                list($prefix, $pk) = explode(':', $key);
                if (isset($map[$prefix])) {

                    foreach ($map[$prefix] as $p) {
                        $to_del[] = $p . $pk;
                    }
                }
            } else {
                //old -> newroad
                $ok = preg_match('~([a-zA-Z]+)([0-9]+)~', $key, $matches);
                if ($ok) {
                    $prefix = $matches[1];
                    $pk = $matches[2];

                    foreach ($map as $pre => $arr) {
                        if (in_array($prefix, $arr)) {
                            $to_del[] = "{$pre}:{$pk}";
                        }
                    }
                }
            }
        }

        return $to_del;
    }

    /**
     * @param array $keys
     * @param int $timeout
     * @return bool
     */
    public function _delMulti(array $keys, $timeout = 0) {
        try {
            Core_Stats::$memcacheCnt++;
            if (Core_Config::get('Core.SQLDebug')) {
                Core_Log::log("Del Multi " . implode(',', $keys), 'MCDebug');
            }
            $this->memcache->deleteMulti($keys, $timeout);
            return true;
        } catch (Exception $e) {
            return false;
        }
    }

    /**
     * @param string
     * @param int $timeout
     * @return bool
     */
    public function _del($key, $timeout = 0) {
        try {
            Core_Stats::$memcacheCnt++;
            if (Core_Config::get('Core.SQLDebug')) {
                Core_Log::log("Del {$key}", 'MCDebug');
            }
            return $this->memcache->delete($key, $timeout);
        } catch (Exception $e) {
            return false;
        }
    }

    /**
     * @see http://www.php.net/manual/zh/memcached.increment.php
     * Make increment/decrement initialize value when it is not available (when using binary protocol).
     * If you dont have the opt binary protocol set the arguments for initial value just return an error 38 - INVALID ARGUMENTS. This is not documented.
     *
     * If it'll save others some head-scratching, the PECL Memcached extension only supports initializing increment (or decrement) values from 2.0.0b2 onwards - i.e. not the version (1.0.2) that comes out of the box with Ubuntu 12.04.
     *
     */
    public function inc($key, $value = 1) {
        try {
            Core_Stats::$memcacheCnt++;
            if (Core_Config::get('Core.SQLDebug')) {
                Core_Log::log("Inc {$key} by {$value}", 'MCDebug');
            }
            return $this->memcache->increment($key, intval($value));
        } catch (Exception $e) {
            return false;
        }
    }

    public function dec($key, $value = 1) {
        try {
            Core_Stats::$memcacheCnt++;
            if (Core_Config::get('Core.SQLDebug')) {
                Core_Log::log("Dec {$key} by {$value}", 'MCDebug');
            }
            return $this->memcache->decrement($key, intval($value));
        } catch (Exception $e) {
            return false;
        }
    }


    public function getError() {
        return $this->memcache->getResultCode() . ':' . $this->memcache->getResultMessage();
    }

    /**
     *
     * @param string $key
     * @param mixed $function the avaliable callback function
     * @param array $params
     * @param int $ttl
     * @return mixed
     */
    public function getAndSave($key, $function, array $params = array(), $ttl = self::TTL_MAX) {
        $ret = $this->get($key);
        if (false === $ret) {
            $ret = call_user_func_array($function, $params);
            if (false !== $ret) {
                $this->set($key, $ret, $ttl);
            }
        }

        return $ret;
    }

    public function close() {
//        $this->memcache->close();
        unset(self::$_instance[$this->name]);
    }

    public function __destruct() {
//        $this->memcache->close();
    }

}
